package com.github.tommas.admintpl.config;

import com.auth0.jwt.algorithms.Algorithm;
import com.github.tommas.admintpl.aop.TypedAdvisorAutoProxyCreator;
import com.github.tommas.admintpl.controller.RequirePermissionController;
import com.github.tommas.admintpl.security.AccountRealm;
import com.github.tommas.admintpl.security.JWTAwareSubjectFactory;
import com.github.tommas.admintpl.security.LoginAuthenticationFilter;
import com.github.tommas.admintpl.security.UserAuthorizationFilter;
import com.github.tommas.admintpl.service.AccountService;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.mgt.SessionStorageEvaluator;
import org.apache.shiro.mgt.SubjectFactory;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.web.config.ShiroFilterConfiguration;
import org.apache.shiro.web.filter.mgt.DefaultFilter;
import org.apache.shiro.web.mgt.DefaultWebSessionStorageEvaluator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.lang.NonNull;

import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Configuration
@Order
public class SecurityConfiguration {
    public static final String LOGIN_URL = "/login";

    @Bean
    public TypedAdvisorAutoProxyCreator<RequirePermissionController> permissionCheckAdvisorAutoProxyCreator() {
        // This AdvisorAutoProxyCreator bean is needed, because when @EnableTransactionManagement(proxyTargetClass = true)
        // is added, the autoconfiguration of Shiro will not create an AdvisorAutoProxyCreator bean.
        return new TypedAdvisorAutoProxyCreator<>(RequirePermissionController.class);
    }

    @Bean
    public SubjectFactory subjectFactory(Algorithm algorithm) {
        return new JWTAwareSubjectFactory(algorithm);
    }

    @Bean
    public SessionStorageEvaluator sessionStorageEvaluator() {
        DefaultWebSessionStorageEvaluator evaluator = new DefaultWebSessionStorageEvaluator();
        evaluator.setSessionStorageEnabled(false);
        return evaluator;
    }

    @Bean
    public Realm realm(AccountService service) {
        AccountRealm realm = new AccountRealm(service);
        realm.setCachingEnabled(true);
        return realm;
    }

    private Map<String, String> filterChainMap() {
        DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();

        chainDefinition.addPathDefinition(LOGIN_URL, DefaultFilter.authc.name());
        chainDefinition.addPathDefinition("/logout", DefaultFilter.logout.name());
        chainDefinition.addPathDefinition("/**", DefaultFilter.user.name());

        return chainDefinition.getFilterChainMap();
    }

    @Bean
    ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();

        filterFactoryBean.setLoginUrl(LOGIN_URL);
        filterFactoryBean.setSecurityManager(securityManager);

        ShiroFilterConfiguration filterConfiguration = new ShiroFilterConfiguration();
        filterFactoryBean.setShiroFilterConfiguration(filterConfiguration);

        List<String> globalFilters = new ArrayList<>();
        globalFilters.add(DefaultFilter.invalidRequest.name());

        // disable session
        // globalFilters.add(DefaultFilter.noSessionCreation.name());

        filterFactoryBean.setGlobalFilters(globalFilters);

        filterFactoryBean.setFilterChainDefinitionMap(filterChainMap());

        // override default filters
        Map<String, Filter> filterMap = filterFactoryBean.getFilters();
        filterMap.put(DefaultFilter.authc.name(), new LoginAuthenticationFilter());
        filterMap.put(DefaultFilter.user.name(), new UserAuthorizationFilter());

        return filterFactoryBean;
    }

    @Bean
    public Algorithm jwtAlgorithm(
            @NonNull
            @Value("${jwt.algorithm.hmac-SHA256-secrete}") String secrete) {
        try {
            return Algorithm.HMAC256(secrete);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}